package tbs.framework.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadFactoryBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import tbs.framework.async.container.IAsyncTaskBroker;
import tbs.framework.async.container.IAsyncTaskContainer;
import tbs.framework.async.container.IAsyncTaskService;
import tbs.framework.async.task.aspects.AsyncTaskAop;
import tbs.framework.async.task.container.SpringAsyncContainer;
import tbs.framework.async.task.container.impl.ImportedAsyncTaskService;
import tbs.framework.async.task.container.impl.SimpleTaskExecutor;
import tbs.framework.async.task.container.impl.ThreadPoolTaskExecutor;
import tbs.framework.constants.BeanNameConstant;
import tbs.framework.interfaces.impls.threads.handlers.LogExceptionHandler;
import tbs.framework.log.ILogger;
import tbs.framework.log.annotations.AutoLogger;
import tbs.framework.properties.AsyncTaskProperty;
import tbs.framework.properties.ExecutorProperty;
import tbs.framework.utils.BeanUtil;
import tbs.framework.utils.ThreadUtil;
import tbs.framework.utils.impls.SimpleThreadUtil;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.concurrent.*;

import static tbs.framework.constants.BeanNameConstant.BUILTIN_ASYNC_TASK_SERVICE;

/**
 * @author abstergo
 */
public class AsyncConfig {
    @Resource
    ExecutorProperty executorProperty;

    @Resource
    AsyncTaskProperty asyncTaskProperty;

    @AutoLogger
    ILogger logger;

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    AsyncTaskAop asyncTaskAop() {
        return new AsyncTaskAop();
    }


    @Bean(BeanNameConstant.BUILTIN_ASYNC_TASK_CALLBACK)
    @ConditionalOnMissingBean(ThreadUtil.IReceiptBroker.class)
    ThreadUtil.IReceiptBroker receiptConsumer() throws Exception {
        return asyncTaskProperty.getReceiptBroker().getConstructor().newInstance();
    }

    @Bean
    @ConditionalOnMissingBean(IAsyncTaskBroker.class)
    IAsyncTaskBroker asyncTaskBroker(List<ExecutorService> executorServices) throws Throwable {
        logger.info("Async task executor count: {}", executorServices.size());
        IAsyncTaskBroker defaultItem = null;
        if (CollUtil.isEmpty(executorServices)) {
            defaultItem = new SimpleTaskExecutor();
        } else {
            defaultItem = new ThreadPoolTaskExecutor(executorServices.get(0));
            logger.info("use threadPoolTaskExecutor by {}", executorServices.get(0));
        }
        return BeanUtil.buildBeanFromProperties(defaultItem, asyncTaskProperty.getTaskBroker(),
            BeanUtil::useEmptyArgs);
    }

    @Bean(BUILTIN_ASYNC_TASK_SERVICE)
    @ConditionalOnMissingBean(value = IAsyncTaskService.class, name = BUILTIN_ASYNC_TASK_SERVICE)
    IAsyncTaskService asyncTaskService(IAsyncTaskContainer container, IAsyncTaskBroker broker) {
        return new ImportedAsyncTaskService(container, broker);
    }

    @Bean(BeanNameConstant.ASYNC_EXECUTOR_EXCEPTION_HANDLER)
    @ConditionalOnMissingBean(value = {Thread.UncaughtExceptionHandler.class},
        name = {BeanNameConstant.ASYNC_EXECUTOR_EXCEPTION_HANDLER})
    Thread.UncaughtExceptionHandler executorServiceUncaughtExceptionHandler() throws Throwable {
        return BeanUtil.buildBeanFromProperties(new LogExceptionHandler(),
            executorProperty.getUncaughtExceptionHandler(), BeanUtil::useEmptyArgs);
    }

    @Bean(name = BeanNameConstant.ASYNC_EXECUTOR_REJECT_HANDLER)
    @ConditionalOnMissingBean(value = {RejectedExecutionHandler.class},
        name = {BeanNameConstant.ASYNC_EXECUTOR_REJECT_HANDLER})
    RejectedExecutionHandler executorServiceRejectHandler() throws Throwable {
        return BeanUtil.buildBeanFromProperties(new ThreadPoolExecutor.CallerRunsPolicy(),
            executorProperty.getRejectedExecutionHandler(), BeanUtil::useEmptyArgs);
    }

    @Bean(name = BeanNameConstant.ASYNC_EXECUTOR)
    @ConditionalOnMissingBean(value = {ExecutorService.class}, name = {BeanNameConstant.ASYNC_EXECUTOR})
    public ExecutorService asyncExecutor(
        @Qualifier(BeanNameConstant.ASYNC_EXECUTOR_EXCEPTION_HANDLER) Thread.UncaughtExceptionHandler exceptionHandler)
        throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        return new ThreadPoolExecutor(this.executorProperty.getCorePoolSize(), this.executorProperty.getMaxPoolSize(),
            this.executorProperty.getKeepAliveTime(), TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(this.executorProperty.getQueueCapacity()),
            new ThreadFactoryBuilder().setNamePrefix("main-pool-").setUncaughtExceptionHandler(exceptionHandler)
                .build(), this.executorProperty.getRejectedExecutionHandler().getConstructor().newInstance());
    }

    @Bean(BeanNameConstant.BUILTIN_THREADUTIL)
    @ConditionalOnMissingBean(value = {ThreadUtil.class}, name = {BeanNameConstant.BUILTIN_THREADUTIL})
    ThreadUtil threadUtil() throws Throwable {
        return BeanUtil.buildBeanFromProperties(new SimpleThreadUtil(), executorProperty.getThreadUtilProvider(),
            BeanUtil::useEmptyArgs);
    }

    @Bean
    @ConditionalOnMissingBean(IAsyncTaskContainer.class)
    @ConditionalOnBean(IAsyncTaskService.class)
    public IAsyncTaskContainer asyncTaskContainer() {
        return new SpringAsyncContainer();
    }

}
